跳到主要内容

Java HTTP编程

HTTP 编程

参考资料 HTTP编程

HTTP 协议是什么这篇就不讲了,这里主要介绍 Java原生的处理 HTTP请求的方法(HTTP 客户端)

Java 标准库提供了基于 HTTP 的包,但是要注意,早期的 JDK版本是通过 HttpURLConnection 访问 HTTP,典型代码如下:

URL url = new URL("http://www.example.com/path/to/target?a=1&b=2");

HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setUseCaches(false);
conn.setConnectTimeout(5000); // 请求超时5秒

// 设置HTTP头:
conn.setRequestProperty("Accept", "*/*");
conn.setRequestProperty("User-Agent", "Mozilla/5.0 (compatible; MSIE 11; Windows NT 5.1)");

// 连接并发送HTTP请求:
conn.connect();

// 判断HTTP响应是否200:
if (conn.getResponseCode() != 200) {
throw new RuntimeException("bad response");
}

// 获取所有响应Header:
Map<String, List<String>> map = conn.getHeaderFields();
for (String key : map.keySet()) {
System.out.println(key + ": " + map.get(key));
}

// 获取响应内容:
InputStream input = conn.getInputStream();
...

JDK11之后的 HttpClient

上述代码编写比较繁琐,并且需要手动处理 InputStream,所以用起来很麻烦。

从 Java 11开始,引入了新的 HttpClient,它使用链式调用的 API,能大大简化 HTTP 的处理。

HttpClient 的 GET 请求

首先需要创建一个全局 HttpClient 实例,因为 HttpClient 内部使用线程池优化多个 HTTP 连接,可以复用:

static HttpClient httpClient = HttpClient.newBuilder().build();

使用 GET 请求获取文本内容代码如下:

public class Main {
// 全局HttpClient:
static HttpClient httpClient = HttpClient.newBuilder().build();

public static void main(String[] args) throws Exception {
String url = "https://www.example.com/";
HttpRequest request = HttpRequest.newBuilder(new URI(url))
// 设置Header:
.header("User-Agent", "Java HttpClient").header("Accept", "*/*")
// 设置超时:
.timeout(Duration.ofSeconds(5))
// 设置版本:
.version(Version.HTTP_2).build();

HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());

// HTTP 允许重复的Header,因此一个Header可对应多个Value:
Map<String, List<String>> headers = response.headers().map();

for (String header : headers.keySet()) {
System.out.println(header + ": " + headers.get(header).get(0));
}

System.out.println(response.body().substring(0, 1024) + "...");
}
}

HttpClient 的 POST 请求

public class Temp {
public static void main(String[] args) throws IOException, InterruptedException {
HttpClient client = HttpClient.newBuilder().build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://www.example.com/"))
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(HttpRequest.BodyPublishers.ofString("name1=value1&name2=value2"))
.build();

HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body());
}
}

第三方 HttpClient 工具

上面那个 HttpClient 的官方提供的,固然很好,但是对象当前依旧大部分项目都是使用的 JDK8 所以对于 HTTP 的处理可能需要借助第三方工具,例如下面这个 HttpClient

HttpClient 是 Apache Jakarta Common 下的子项目,用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建议。

HttpClient的主要功能:

  • 实现了所有 HTTP 的方法(GET、POST、PUT、HEAD、DELETE、HEAD、OPTIONS 等)
  • 支持 HTTPS 协议
  • 支持代理服务器(Nginx等)等
  • 支持自动(跳转)转向
<!-- 使用这个 commons-io 库来下载文件 -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.8.0</version>
</dependency>


<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>

get请求

public class Test {

public static void main(String[] args) throws IOException {
HttpClient client = HttpClients.createDefault();// 创建默认http连接
HttpGet get = new HttpGet("http://127.0.0.1/dispatch/accessResult");// 创建一个post请求

HttpResponse response = client.execute(get);// 用http连接去执行get请求并且获得http响应
HttpEntity entity = response.getEntity();// 从response中取到响实体
String html = EntityUtils.toString(entity);// 把响应实体转成文本
System.out.println(html);

}
}

post请求

public class Test {

public static void main(String[] args) throws IOException {
HttpClient client = HttpClients.createDefault();// 创建默认http连接
HttpPost post = new HttpPost("http://127.0.0.1/dispatch/accessResult"); // 创建一个post请求

List<NameValuePair> paramList = new ArrayList<NameValuePair>();

paramList.add(new BasicNameValuePair("carNO", "京C004"));//传递的参数

// 把参转码后放入请求实体中
HttpEntity entitya = new UrlEncodedFormEntity(paramList, "utf-8");

post.setEntity(entitya);// 把请求实体放post请求中
HttpResponse response = client.execute(post);// 用http连接去执行get请求并且获得http响应
HttpEntity entity = response.getEntity();// 从response中取到响实体
String html = EntityUtils.toString(entity);// 把响应实体转成文本
System.out.println(html);
}
}

下载图片

public static void downloadHttpUrl(HttpClient client, String url, String dir, String fileName) {
// 发送get请求
HttpGet request = new HttpGet(url);
// 设置请求和传输超时时间
RequestConfig requestConfig = RequestConfig.custom()
.setSocketTimeout(50000).setConnectTimeout(50000).build();
request.setConfig(requestConfig);

//设置请求头
request.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.79 Safari/537.1");

try {
HttpResponse response = client.execute(request);
if (HttpStatus.SC_OK == response.getStatusLine().getStatusCode()) {
HttpEntity entity = response.getEntity();
InputStream in = entity.getContent();
FileUtils.copyInputStreamToFile(in, new File(dir + fileName));
System.out.println("下载图片成功:" + dir + fileName);
} else {
System.out.println("下载图片失败:" + dir + fileName);
}
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
} finally {
request.releaseConnection();
}
}

URL工具类

参考资料 Java URL处理

URL(Uniform Resource Locator)中文名为统一资源定位符,有时也被俗称为网页地址。表示为互联网上的资源,如网页或者FTP地址。

URL 构造方法

// 通过给定的参数(协议、主机名、端口号、文件名)创建URL。
public URL(String protocol, String host, int port, String file) throws MalformedURLException.

// 使用指定的协议、主机名、文件名创建URL,端口使用协议的默认端口。
public URL(String protocol, String host, String file) throws MalformedURLException

// 通过给定的URL字符串创建URL
public URL(String url) throws MalformedURLException

// 使用基地址和相对URL创建
public URL(URL context, String url) throws MalformedURLException

URL 的常用方法

// 返回URL路径部分。
public String getPath()

// 返回URL查询部分。
public String getQuery()

// 获取此 URL 的授权部分。
public String getAuthority()

// 返回URL端口部分
public int getPort()

// 返回协议的默认端口号。
public int getDefaultPort()

// 返回URL的协议
public String getProtocol()

// 返回URL的主机
public String getHost()

// 返回URL文件名部分
public String getFile()

// 获取此 URL 的锚点
public String getRef()

// 打开一个URL连接,并运行客户端访问资源。
public URLConnection openConnection() throws IOException

使用实例

public class URLDemo {
public static void main(String [] args) {
try
{
URL url = new URL("http://www.runoob.com/index.html?language=cn#j2se");
System.out.println("URL 为:" + url.toString());
System.out.println("协议为:" + url.getProtocol());
System.out.println("验证信息:" + url.getAuthority());
System.out.println("文件名及请求参数:" + url.getFile());
System.out.println("主机名:" + url.getHost());
System.out.println("路径:" + url.getPath());
System.out.println("端口:" + url.getPort());
System.out.println("默认端口:" + url.getDefaultPort());
System.out.println("请求参数:" + url.getQuery());
System.out.println("定位位置:" + url.getRef());
} catch(IOException e) {
e.printStackTrace();
}
}
}

输出为:

URL 为:http://www.runoob.com/index.html?language=cn#j2se
协议为:http
验证信息:www.runoob.com
文件名及请求参数:/index.html?language=cn
主机名:www.runoob.com
路径:/index.html
端口:-1
默认端口:80
请求参数:language=cn
定位位置:j2se

原生的 URL 下载数据

统一资源定位符: 定位互联网上的资源

协议://ip地址:端口号 / 项目名 / 资源名

解析 URL

public class URLDemo {
public static void main(String[] args) throws MalformedURLException {
//注意这只是解析url,并不需要连接上这个地址
URL url = new URL("http://localhost:8080/helloworld/index.jsp?user=alsritter&pwd=1234");
System.out.println(
"协议:" + url.getProtocol() + "\n" +
"主机host:" + url.getHost() + "\n" +
"端口:" + url.getPort() + "\n" +
"路径:" + url.getPath() + "\n" +
"文件:" + url.getFile() + "\n" +
"获取参数:" + url.getQuery()
);
}
}

//------输出------->
// 协议:http
// 主机host:localhost
// 端口:8080
// 路径:/helloworld/index.jsp
// 文件:/helloworld/index.jsp?user=alsritter&pwd=1234
// 获取参数:user=alsritter&pwd=1234

使用URL下载资源

网络请求只需要连接再用 io 下载下来就好了

/**
* 下载网络资源
*
* @author alsritter
* @version 1.0
**/
public class URLDownLoad {
public static void main(String[] args) throws IOException {
// 下载地址
URL url = new URL("https://image.alsritter.icu/img/headPortrait.jpg");
// 连接上这个资源
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
InputStream is = urlConnection.getInputStream();
// 取得文件名(要考虑参数不存在的情况,所以使用三目运算)
String fileName = url.getFile().substring(url.getFile().lastIndexOf("/")+1, !url.getFile().contains("?") ?url.getFile().length():url.getFile().indexOf("?"));
System.out.println(fileName);
FileOutputStream fos = new FileOutputStream(fileName);

byte[] buffer = new byte[1024];
int len;
while ((len=is.read(buffer))!=-1){
fos.write(buffer,0,len);
}

fos.close();
is.close();
urlConnection.disconnect();
}
}

URLConnections 工具类

上面的 openConnection() 返回一个 java.net.URLConnection 对象

  • 如果连接 HTTP 协议的 URL, openConnection() 方法返回 HttpURLConnection 对象。
  • 如果连接的URL为一个 JAR 文件, openConnection() 方法将返回 JarURLConnection 对象。

URLConnection 常用方法

// 检索URL链接内容
Object getContent()

// 检索URL链接内容
Object getContent(Class[] classes)

// 返回头部 content-encoding 字段值。
String getContentEncoding()

// 返回头部 content-length字段值
int getContentLength()

// 返回头部 content-type 字段值
String getContentType()

// 返回头部 last-modified 字段值。
int getLastModified()

// 返回头部 expires 字段值。
long getExpiration()

// 返回对象的 ifModifiedSince 字段值。
long getIfModifiedSince()

// URL 连接可用于输入和/或输出。如果打算使用 URL 连接进行输入,则将 DoInput 标志设置为 true;如果不打算使用,则设置为 false。默认值为 true。
public void setDoInput(boolean input)

// URL 连接可用于输入和/或输出。如果打算使用 URL 连接进行输出,则将 DoOutput 标志设置为 true;如果不打算使用,则设置为 false。默认值为 false。
public void setDoOutput(boolean output)

// 返回URL的输入流,用于读取资源
public InputStream getInputStream() throws IOException

// 返回URL的输出流, 用于写入资源。
public OutputStream getOutputStream() throws IOException

// 返回 URLConnection 对象连接的URL
public URL getURL()

使用实例

public class URLConnDemo {
public static void main(String [] args) {
try {
URL url = new URL("http://www.runoob.com");
URLConnection urlConnection = url.openConnection();
HttpURLConnection connection = null;

if(urlConnection instanceof HttpURLConnection) {
connection = (HttpURLConnection) urlConnection;
} else {
System.out.println("请输入 URL 地址");
return;
}

BufferedReader in = new BufferedReader(
new InputStreamReader(connection.getInputStream()));
String urlString = "";
String current;

while((current = in.readLine()) != null) {
urlString += current;
}

System.out.println(urlString);

} catch(IOException e) {
e.printStackTrace();
}
}
}

输出为:

.....这里会输出菜鸟教程首页(http://www.runoob.com)的 HTML 内容.....

URL 使用示例

/**
* 二维码工具
*/
@Slf4j
public final class QrCodeBuilder {
private QrCodeBuilder() {
}

/**
* 接口调用凭证 access_token
*/
public static String postToken(String appId, String appKey) throws IOException {

String requestUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + appId + "&secret=" + appKey;
URL url = new URL(requestUrl);
// 打开和 URL 之间的连接
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
// 设置通用的请求属性
connection.setRequestProperty("Content-Type", "application/json");
connection.setRequestProperty("Connection", "Keep-Alive");
connection.setUseCaches(false);
connection.setDoOutput(true);
connection.setDoInput(true);

// 得到请求的输出流对象
DataOutputStream out = new DataOutputStream(connection.getOutputStream());
out.writeBytes("");
out.flush();
out.close();

// 建立实际的连接
connection.connect();
// 定义 BufferedReader输入流来读取URL的响应
BufferedReader in;
if (requestUrl.contains("nlp"))
in = new BufferedReader(new InputStreamReader(connection.getInputStream(), "GBK"));
else
in = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8));
StringBuilder result = new StringBuilder();
String getLine;
while ((getLine = in.readLine()) != null) {
result.append(getLine);
}
in.close();
JSONObject jsonObject = JSONObject.parseObject(result.toString());
return jsonObject.getString("access_token");
}

/**
* 生成微信小程序二维码
*
* @param page 当前小程序相对页面 必须是已经发布的小程序存在的页面(否则报错),例如 pages/index/index, 根路径前不要填加 /,
* 不能携带参数(参数请放在scene字段里),如果不填写这个字段,默认跳主页面
* @param scene 最大32个可见字符,只支持数字,大小写英文以及部分特殊字符:!#$&'()*+,/:;=?@-._~,其它字符请自行编码为合法字
* 符(因不支持%,中文无法使用 urlencode 处理,请使用其他编码方式)
* @param accessToken 接口调用凭证
* @return Base64 编码的二维码图片
*/
public static String generateQrCodeToBase64(String page, String scene, String accessToken) throws IOException {
byte[] data = generateQrCodeToByteArray(page, scene, accessToken);
return new String(Base64.encodeBase64(data), StandardCharsets.UTF_8);
}

/**
* 生成微信小程序二维码上传到百度云里面返回链接
*
* @param client 百度 BOS 上传客户端
* @param bucketName 上传的位置
* @param page 当前小程序相对页面 必须是已经发布的小程序存在的页面(否则报错),例如 pages/index/index, 根路径前不要填加 /,
* 不能携带参数(参数请放在scene字段里),如果不填写这个字段,默认跳主页面
* @param scene 最大32个可见字符,只支持数字,大小写英文以及部分特殊字符:!#$&'()*+,/:;=?@-._~,其它字符请自行编码为合法字
* 符(因不支持%,中文无法使用 urlencode 处理,请使用其他编码方式)
* @param accessToken 接口调用凭证
* @return 二维码图片的 URL
*/
public static String generateQrCodeToURL(BosClient client, String bucketName, String page, String scene, String accessToken) throws IOException {
byte[] data = generateQrCodeToByteArray(page, scene, accessToken);
String newFileName = DateUtil.getCurrFullDateTimeNoFlag() + RandomUtils.getRandomString(2) + ".png"; // 保存后的文件名称
BosUtils.uploadByteToBos(client, data, bucketName, newFileName);
// 获取文件下载 URL
return BosUtils.generatePresignedUrl(client, bucketName, newFileName, -1);
}


/**
* 生成微信小程序二维码
*
* @param page 当前小程序相对页面 必须是已经发布的小程序存在的页面(否则报错),例如 pages/index/index, 根路径前不要填加 /,
* 不能携带参数(参数请放在scene字段里),如果不填写这个字段,默认跳主页面
* @param scene 最大32个可见字符,只支持数字,大小写英文以及部分特殊字符:!#$&'()*+,/:;=?@-._~,其它字符请自行编码为合法字
* 符(因不支持%,中文无法使用 urlencode 处理,请使用其他编码方式)
* @param accessToken 接口调用凭证
* @return 图片的 byte[]
*/
public static byte[] generateQrCodeToByteArray(String page, String scene, String accessToken) throws IOException {
HttpURLConnection httpURLConnection = null;
byte[] data = null;
try {
//调用微信接口生成二维码
URL url = new URL("https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=" + accessToken);

httpURLConnection = (HttpURLConnection) url.openConnection();
httpURLConnection.setRequestMethod("POST");// 提交模式
// conn.setConnectTimeout(10000);//连接超时 单位毫秒
// conn.setReadTimeout(2000);//读取超时 单位毫秒
// 发送POST请求必须设置如下两行
httpURLConnection.setDoOutput(true);
httpURLConnection.setDoInput(true);


// 获取 URLConnection 对象对应的写入流
try (PrintWriter printWriter = new PrintWriter(httpURLConnection.getOutputStream())) {
// 发送请求参数
JSONObject paramJson = new JSONObject();
//这就是你二维码里携带的参数 String型 名称不可变
paramJson.put("scene", scene);
//注意该接口传入的是page而不是path
paramJson.put("page", page);
//这是设置扫描二维码后跳转的页面
paramJson.put("width", 200);
paramJson.put("is_hyaline", true);
paramJson.put("auto_color", true);
printWriter.write(paramJson.toString());
// flush输出流的缓冲
printWriter.flush();
}

try (BufferedInputStream bis = new BufferedInputStream(httpURLConnection.getInputStream());) {
//开始获取数据
data = inputToByte(bis);
// 如何返回值的大小小于这个值,那肯定不是图片
if (data.length < 2500) {
String json = new String(data);
JSONObject response = JSONObject.parseObject(json);
String errcode = response.getString("errcode");
if (errcode.equals("45009")) {
throw new BusinessException("调用分钟频率受限(目前5000次/分钟),如需大量小程序码,建议预生成。");
} else if (errcode.equals("41030")) {
throw new BusinessException("所传 page 页面不存在,或者小程序没有发布");
} else if (!errcode.equals("0")) {
throw new BusinessException("二维码生成未知错误");
}
}
}

} finally {
if (httpURLConnection != null)
httpURLConnection.disconnect();
}

return data;
}

/**
* 流转 byte 数组
*
* @param inputStream 输入流
* @return 流里面的内容
* @throws IOException IO 错误
*/
private static byte[] inputToByte(InputStream inputStream) throws IOException {
byte[] data = null;
try (ByteArrayOutputStream swapStream = new ByteArrayOutputStream()) {
byte[] buff = new byte[1024];
int rc;
while ((rc = inputStream.read(buff, 0, 1024)) > 0) {
swapStream.write(buff, 0, rc);
}
data = swapStream.toByteArray();
}
return data;
}
}